package org.nativejpa.query;

import org.nativejpa.cache.CacheFactory;
import org.nativejpa.cache.Cache;

import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 * @create 2009-3-18 14:43:10
 */
public class CacheQuerySynchronization implements QuerySynchronization {
    static Log logger = LogFactory.getLog(CacheQuerySynchronization.class);

    /**
     * get Cache by sqlquery
     */
    private Cache getCache(SQLQuery query) {
        SQLTemplate sqlTempalte = query.getSQLTemplate();
        if (sqlTempalte instanceof NamedSQLTemplate) { // only cache NamedQuery
            String cacheName = ((NamedSQLTemplate) sqlTempalte).getCacheName();
            CacheFactory cacheFactory = query.getCacheFactory();
            return cacheFactory.newCache(cacheName);
        }
        else {
            return null;
        }
    }

    public void beforeExecuteQuery(SQLQuery query) {
        Cache cache = getCache(query);
        if (cache != null) {
            CacheKey key = CacheKey.newCacheKey(query);
            if (cache.contains(key)) {
                query.setResult(cache.get(key));
            }
        }
    }

    public void beforeExecuteUpdate(SQLQuery query) {
        
    }

    public void afterExecuteQuery(SQLQuery query, boolean success) {
        if (!success) return;
        Cache cache = getCache(query);
        if (cache != null) {
            Object result = query.getResult();
            CacheKey key = CacheKey.newCacheKey(query);

            if (result == null) {
                cache.remove(key);
            }
            else if (!(result instanceof Serializable)) {
                logger.warn("Store cache failed, result is not Serializable! " + result);
                cache.remove(key);
            }
            else {
                cache.put(key, (Serializable) result);
            }
        }
    }

    public void afterExecuteUpdate(SQLQuery query, boolean success) {
        if (!success) return;
        Cache cache = getCache(query);
        if(cache != null) {
            cache.clear();
        }

        // clear default cache
        CacheFactory cacheFactory = query.getCacheFactory();
        Cache defaultCache = cacheFactory.newCache(NamedSQLTemplate.DEFAULT_CACHE_NAME);
        defaultCache.clear();
    }

    //实现完整的 equals and hashCode
    static class CacheKey implements Serializable {
        private String templateName;
        private Map<String, Object> parameterMap = new HashMap<String, Object>();
        private int startPosition = 0;
        private int maxResult = Integer.MAX_VALUE;

        static CacheKey newCacheKey(SQLQuery query) {
            return new CacheKey(query.getSQLTemplate().getTemplateSQL(), query.getParameterMap(), query.getFirstResult(), query.getMaxResult());
        }

        /**
         * @param templateName  用 templateSQL 做key，保证 AUTOSQL的安全
         * @param parameterMap
         * @param startPosition
         * @param maxResult
         */
        private CacheKey(String templateName, Map<String, Object> parameterMap, int startPosition, int maxResult) {
            this.templateName = templateName;
            this.parameterMap.putAll(parameterMap);
            this.startPosition = startPosition;
            this.maxResult = maxResult;
        }

        public String toString() {
            return templateName + ", ParameterMap" + parameterMap.toString();
        }

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            CacheKey cacheKey = (CacheKey) o;
            return templateName.equals(cacheKey.templateName)
                    && (startPosition == cacheKey.startPosition)
                    && (maxResult == cacheKey.maxResult)
                    && parameterMap.equals(cacheKey.parameterMap);
        }

        public int hashCode() {
            int result;
            result = templateName.hashCode();
            result = 31 * result + parameterMap.hashCode();
            return result;
        }
    }
}
